/*
 * Exploder
 * 
 * Tero Pyylampi (218437), Jussi Tuurinkoski (211594)
 *
 * Camera object for the game,
 * move the parallax node and handles the coordinate transformations
 */

#include "GameCamera.h"
#include "../Common.h"

const float GameCamera::mMaxZoom = 2.1f;
const float GameCamera::mMinZoom = 1.0f;
const float GameCamera::mZoomAcceleration = 0.8f;
const float GameCamera::mAcceleration = 0.83f;

// default constructor
GameCamera::GameCamera( cocos2d::CCParallaxNode *node ) : 
		mParallaxNode( node ), mCameraLockState( true ), mMoveActionValid( false ),
		mCurrentZoom( 1.0f ), mZoomSpeed( 0.0f ), mCameraAction( 0 ) {
	mCurrentSpeed = ccp( 0, 0 );
	mPosOffset = ccp( 0, 0 );
}

// default destructor
GameCamera::~GameCamera() { }

// update camera
void GameCamera::update( cocos2d::ccTime dt ) {
	if( mMoveActionValid ) {
		// check the limits for the position
		mTmpPosition = ccpAdd( mParallaxNode->getPosition(), mMoveCamera );
		// reset switch
		mMoveActionValid = false;
	}
	else {
		// possibly run an action
		if( mCameraAction != 0 )
			updateCameraAction( dt );

		// check the zoom speed
		if( mZoomSpeed > 0.04f && mCameraAction == 0 )
			mZoomSpeed = 0.04f;
		else if( mZoomSpeed < -0.04f && mCameraAction == 0 )
			mZoomSpeed = -0.04f;

		// calculate new zoom
		mCurrentZoom += mZoomSpeed;
		// check the limits for the zoom
		if( mCurrentZoom > mMaxZoom )
			mCurrentZoom = mMaxZoom;
		if( mCurrentZoom < mMinZoom )
			mCurrentZoom = mMinZoom;

		// set zoom stage
		mParallaxNode->setScaleX( mCurrentZoom );
		mParallaxNode->setScaleY( mCurrentZoom );

		// calculate final position
		mFinalSpeed = mCurrentSpeed;
		mFinalSpeed.x *= mCurrentZoom * 0.75f;
		mFinalSpeed.y *= mCurrentZoom * 0.75f;

		// final speed has to be varied towards to zooming center
		if( abs( mZoomSpeed ) > 0.00001f ) {
			mFinalSpeed.x = ( mZoomPoint.x - mParallaxNode->getPosition().x ) * mZoomSpeed * -1.75f;
			mFinalSpeed.y = ( mZoomPoint.y - mParallaxNode->getPosition().y ) * mZoomSpeed * -1.75f;
		}
		// check maximum limits for camera speed
		else {
			if( mFinalSpeed.x > WIN_WIDTH / 50.0f ) mFinalSpeed.x = WIN_WIDTH / 50.0f;
			else if( mFinalSpeed.x < -WIN_WIDTH / 50.0f ) mFinalSpeed.x = -WIN_WIDTH / 50.0f;
			if( mFinalSpeed.y > WIN_HEIGHT / 30.0f ) mFinalSpeed.y = WIN_HEIGHT / 30.0f;
			else if( mFinalSpeed.y < -WIN_HEIGHT / 30.0f ) mFinalSpeed.y = -WIN_HEIGHT / 30.0f;
		}

		// check the limits for the position
		mTmpPosition = ccpAdd( mParallaxNode->getPosition(), mFinalSpeed );
		// note position offset & reset it afterwards
		mTmpPosition = ccpAdd( mTmpPosition, mPosOffset );
		mPosOffset.x = mPosOffset.y = 0;
	}

	// x-direction
	if( mTmpPosition.x > WIN_WIDTH * 1.35f * mCurrentZoom )
		mTmpPosition.x = WIN_WIDTH * 1.35f * mCurrentZoom;
	else if( mTmpPosition.x < WIN_WIDTH - WIN_WIDTH * 1.35f * mCurrentZoom )
		mTmpPosition.x = WIN_WIDTH - WIN_WIDTH * 1.35f * mCurrentZoom;
	// y-direction
	if( mTmpPosition.y > WIN_HEIGHT * 0.6f * mCurrentZoom )
		mTmpPosition.y = WIN_HEIGHT * 0.6f * mCurrentZoom;
	else if( mTmpPosition.y < WIN_HEIGHT - WIN_HEIGHT * 1.1f * mCurrentZoom )
		mTmpPosition.y = WIN_HEIGHT - WIN_HEIGHT * 1.1f * mCurrentZoom;

	// set the position
	mParallaxNode->setPosition( mTmpPosition );
	
	// apply reductions
	mCurrentSpeed.x *= mAcceleration;
	mCurrentSpeed.y *= mAcceleration;
	mZoomSpeed *= mZoomAcceleration;

	// for rounding off errors
	if( abs( mCurrentSpeed.x ) < 0.001f )
		mCurrentSpeed.x = 0;
	if( abs( mCurrentSpeed.y ) < 0.001f )
		mCurrentSpeed.y = 0;
	if( abs( mZoomSpeed ) < 0.00001f )
		mZoomSpeed = 0;
}

// set acceleration, main function to move camera
void GameCamera::setCameraSpeed( cocos2d::CCPoint &speed ) {
	mCurrentSpeed = ccp( speed.x, speed.y );
}

// move camera by certain amount
void GameCamera::moveCamera( cocos2d::CCPoint &amount ) {
	mMoveCamera = ccp( amount.x, amount.y );
	mMoveActionValid = true;
	mCameraAction = 0;
}

// to move the world
void GameCamera::setWorldPosition( cocos2d::CCPoint &position ) {
	mParallaxNode->setPosition( position.x, position.y );
}

// set zoom speed
void GameCamera::setZoomSpeed( float speed, cocos2d::CCPoint &zoomPosition ) {
	mZoomSpeed = speed;
	mZoomPoint = zoomPosition;
}

// position offset for effects
void GameCamera::setPositionOffset( float x, float y ) {
	mPosOffset.x = x;
	mPosOffset.y = y;
}

// set new target point for camera to look
void GameCamera::setNewCameraTarget( float x, float y, float timeToTake, ActionStyle style ) {
	//// delete old one
	//if( mCameraAction != 0 ) delete mCameraAction;

	//// fix coordinates
	//x = -x;
	//y = GameWorldCreator::WORLD_HEIGHT - y;

	//// create a new action
	//mCameraAction = new MCameraAction;
	//mCameraAction->framePassed = false;
	//mCameraAction->startingPos = mParallaxNode->getPosition();
	//mCameraAction->finalPos = convertWorldToScreen( x, y );
	//mCameraAction->startingZoom = mCurrentZoom;
	//mCameraAction->finalZoom = mCurrentZoom;
	//mCameraAction->transitionTime = timeToTake;
	//mCameraAction->timeLeft = timeToTake;
	//mCameraAction->actionStyle = style;
}

// set new zoom level target
void GameCamera::setNewZoomTarget( float x, float y, float zoomLevel, float timeToTake, ActionStyle style ) {
	//// delete old one
	//if( mCameraAction != 0 ) delete mCameraAction;

	//// fix coordinates
	//x = -x;
	//y = GameWorldCreator::WORLD_HEIGHT - y;

	//// create a new action
	//mCameraAction = new MCameraAction;
	//mCameraAction->framePassed = false;
	//mCameraAction->startingPos = mParallaxNode->getPosition();
	//mCameraAction->finalPos = convertWorldToScreen( x, y );
	//mCameraAction->startingZoom = mCurrentZoom;
	//mCameraAction->finalZoom = zoomLevel;
	//mCameraAction->transitionTime = timeToTake;
	//mCameraAction->timeLeft = timeToTake;
	//mCameraAction->actionStyle = style;
}

// absolute value function to keep android compiler happy...
float GameCamera::abs( float value ) {
	if( value < 0 )
		return -value;
	return value;
}

// convert world coordinates to screen coordinates
b2Vec2 GameCamera::convertWorldToScreen( b2Vec2 &world ) {
	return b2Vec2( ( world.x + WORLD_WIDTH / 2.0f ) / WORLD_WIDTH * WIN_WIDTH, 
		world.y / WORLD_HEIGHT * WIN_HEIGHT );
}
cocos2d::CCPoint GameCamera::convertWorldToScreen( float world_x, float world_y ) {
	return ccp( ( world_x + WORLD_WIDTH / 2.0f ) / WORLD_WIDTH * WIN_WIDTH, 
		world_y / WORLD_HEIGHT * WIN_HEIGHT );
}

// convert screen coordinates to world coordinates
b2Vec2 GameCamera::convertScreenToWorld( b2Vec2 &screen ) {
	return b2Vec2( screen.x / WIN_WIDTH * WORLD_WIDTH - WORLD_WIDTH / 2.0f,
		screen.y / WIN_HEIGHT * WORLD_HEIGHT );
}
cocos2d::CCPoint GameCamera::convertScreenToWorld( float screen_x, float screen_y ) {
	return ccp( screen_x / WIN_WIDTH * WORLD_WIDTH - WORLD_WIDTH / 2.0f,
		screen_y / WIN_HEIGHT * WORLD_HEIGHT );
}

// set new camera lock state
void GameCamera::setCameraLockState( bool state ) {
	mCameraLockState = state;
}

// get current camera lock state
bool GameCamera::getCameraLockState() {
	return mCameraLockState;
}

// zoom level
float GameCamera::getCurrentZoomLevel() {
	return mCurrentZoom;
}

// zoom speed
float GameCamera::getCurrentZoomSpeed() {
	return mZoomSpeed;
}

// current position
cocos2d::CCPoint GameCamera::getCurrentPosition() {
	return mParallaxNode->getPosition();
}
//b2Vec2 GameCamera::getCurrentPositionWorldCoords() {
//	cocos2d::CCPoint pos = mParallaxNode->getPosition();
//	cocos2d::CCPoint tmp = convertScreenToWorld( WIN_WIDTH - pos.x, WIN_HEIGHT - pos.y );
//	b2Vec2 coordinates( tmp.x, tmp.y );
//	return coordinates;
//}

// updates camera action
void GameCamera::updateCameraAction( cocos2d::ccTime dt ) {
	// skip first frame
	if( mCameraAction->framePassed == false )
		mCameraAction->framePassed = true;
	
	// normally update
	else {
		// decrease time
		mCameraAction->timeLeft -= dt;

		// reached the end
		if( mCameraAction->timeLeft <= 0.0f ) {
			// correct values
			mParallaxNode->setPosition( mCameraAction->finalPos );
			mCurrentZoom = mCameraAction->finalZoom;
			// delete this action
			delete mCameraAction;
			mCameraAction = 0;
		}

		// normally move
		else {
			// calculate effect
			float mEffectProgress = 0.0f;
			if( mCameraAction->actionStyle == ACTION_LINEAR )
				mEffectProgress = mCameraAction->timeLeft / mCameraAction->transitionTime;
			else if( mCameraAction->actionStyle == ACTION_SLOWING )
				mEffectProgress = std::pow( mCameraAction->timeLeft, 2.0f ) / std::pow( mCameraAction->transitionTime, 2.0f );
			else if( mCameraAction->actionStyle == ACTION_SPEEDING )
				mEffectProgress = std::pow( mCameraAction->timeLeft, 0.5f ) / std::pow( mCameraAction->transitionTime, 0.5f );
			else if( mCameraAction->actionStyle == ACTION_COMBINED ) {
				if( mCameraAction->timeLeft >= ( mCameraAction->transitionTime / 2.0f ) )
					mEffectProgress = std::pow( mCameraAction->timeLeft - ( mCameraAction->transitionTime / 2.0f ), 0.5f ) / std::pow( mCameraAction->transitionTime / 2.0f, 0.5f ) / 2.0f + 0.5f;
				else
					mEffectProgress = std::pow( mCameraAction->timeLeft, 2.0f ) / std::pow( mCameraAction->transitionTime / 2.0f, 2.0f ) / 2.0f;
			}

			// set camera position
			float newX = ( 1.0f - mEffectProgress ) * ( mCameraAction->finalPos.x - mCameraAction->startingPos.x ) + mCameraAction->startingPos.x;
			float newY = ( 1.0f - mEffectProgress ) * ( mCameraAction->finalPos.y - mCameraAction->startingPos.y ) + mCameraAction->startingPos.y;
			mParallaxNode->setPosition( newX, newY );

			// set zoom position
			mCurrentZoom = ( 1.0f - mEffectProgress ) * ( mCameraAction->finalZoom - mCameraAction->startingZoom ) + mCameraAction->startingZoom;
			mZoomSpeed = 0.0f;
		}
	}
}